iT邦幫忙

2024 iThome 鐵人賽

DAY 23
0

每天的專案會同步到 GitLab 上,可以前往 GitLab 查看,有興趣的朋友歡迎留言或來信討論,我的信箱是 nickchen1998@gmail.com

本次爬文的目標是 衛福部 台灣 e 院

警語:本篇所介紹的網路爬蟲技術僅供教學與研究用途,請勿將其用於攻擊行為或商業利用,違反相關法律責任自負。

前言

在網路爬蟲的世界中,靜態網頁的資料擷取相對簡單,但當遇到需要 JavaScript 生成內容的動態網頁時,就需要使用像 Selenium 這樣的工具來模擬瀏覽器行為。今天,我們將深入探討如何使用 Selenium 來擷取動態網頁的資料,並逐步解析程式碼。

程式碼解析

我們將以下列程式碼為例,逐段進行分析。

import re
import time
from pprint import pprint
from datetime import datetime
from selenium.webdriver import Chrome
from selenium.webdriver.common.by import By

首先,我們引入所需的模組:

  • re:用於正則表達式的匹配。
  • time:控制程式的暫停時間。
  • pprint:美化輸出字典資料。
  • datetime:處理日期和時間。
  • selenium.webdriver:控制瀏覽器的核心模組。

初始化瀏覽器並打開目標網頁

browser = Chrome()
browser.get('https://sp1.hso.mohw.gov.tw/doctor/Often_question/type_detail.php?q_type=排便問題&UrlClass=肝膽腸胃科')
time.sleep(10)
  • browser = Chrome():初始化一個 Chrome 瀏覽器實例。
  • browser.get(url):打開指定的 URL。
  • time.sleep(10):等待 10 秒,確保網頁內容完全載入。

設定初始變數

category = "排便問題"
doctor_department = "肝膽腸胃科"

我們設定了兩個變數:

  • category:問題的分類。
  • doctor_department:醫生所屬的科別。

迭代網頁元素並擷取資料

for paragraph in browser.find_elements(By.CSS_SELECTOR, "ul.QAunit"):

使用 CSS 選擇器 ul.QAunit 找到所有相關的問答單元,並開始迭代。

擷取主題

time.sleep(10)
subject = paragraph.find_element(By.CSS_SELECTOR, "li.subject").text
  • 從當前的問答單元中找到 li.subject,並取得其文字內容,存入 subject

擷取提問者資訊

asker_info = paragraph.find_element(By.CSS_SELECTOR, "li.asker").text
match = re.search(r'/(男|女)/.*?,(\d{4}/\d{2}/\d{2})', asker_info)
gender = match.group(1)
question_time = datetime.strptime(match.group(2), '%Y/%m/%d')
  • 找到 li.asker,取得提問者的資訊。
  • 使用正則表達式匹配性別和提問時間。
    • r'/(男|女)/.*?,(\d{4}/\d{2}/\d{2})':匹配格式如「/男/XXX,2023/10/01」的字串。
  • gender:提取到的性別。
  • question_time:將提問時間轉換為 datetime 對象。

擷取問題內容

question = paragraph.find_element(By.CSS_SELECTOR, "li.ask").text
  • 找到 li.ask,取得問題的詳細內容。

擷取回答內容

answer = paragraph.find_element(By.CSS_SELECTOR, "li.ans").text
  • 找到 li.ans,取得回答的內容。

擷取醫生資訊

doctor_info = paragraph.find_element(By.CSS_SELECTOR, "li.doctor").text
match = re.search(r'/([\u4e00-\u9fa5]+),\s*(\d{4}/\d{2}/\d{2})', doctor_info)
doctor_name = match.group(1)
answer_time = datetime.strptime(match.group(2), '%Y/%m/%d')
  • 找到 li.doctor,取得醫生的資訊。
  • 使用正則表達式匹配醫生姓名和回答時間。
    • r'/([\u4e00-\u9fa5]+),\s*(\d{4}/\d{2}/\d{2})':匹配格式如「/張醫師, 2023/10/02」的字串。
  • doctor_name:提取到的醫生姓名。
  • answer_time:將回答時間轉換為 datetime 對象。

擷取瀏覽次數

view_info = paragraph.find_element(By.CSS_SELECTOR, "li.count").text
match = re.search(r'(\d+)', view_info)
view_amount = int(match.group(1)) if match else 0
  • 找到 li.count,取得瀏覽次數的資訊。
  • 使用正則表達式提取數字部分。
  • 如果匹配成功,將其轉換為整數,否則設為 0。

整合資料並輸出

data = dict(
    category=category,
    subject=subject,
    question=question,
    gender=gender,
    question_time=question_time,
    answer=answer,
    doctor_name=doctor_name,
    doctor_department=doctor_department,
    answer_time=answer_time,
    view_amount=view_amount
)
pprint(data)
break
  • 將所有提取到的資訊整合到一個字典 data 中。
  • 使用 pprint 美化輸出字典內容。
  • break:只處理第一個問答單元,退出迴圈。

關閉瀏覽器

browser.quit()
  • 關閉瀏覽器,釋放資源。

執行結果

執行程式後,我們會得到類似以下的輸出:

{'answer': '醫生的回答內容...',
 'answer_time': datetime.datetime(2023, 10, 2, 0, 0),
 'category': '排便問題',
 'doctor_department': '肝膽腸胃科',
 'doctor_name': '張醫師',
 'gender': '男',
 'question': '提問者的問題內容...',
 'question_time': datetime.datetime(2023, 10, 1, 0, 0),
 'subject': '問題的主題',
 'view_amount': 123}

這表示我們成功地從動態網頁中提取了所需的資料。

完整程式碼

import re
import time
from pprint import pprint
from datetime import datetime
from selenium.webdriver import Chrome
from selenium.webdriver.common.by import By
from selenium.common.exceptions import NoSuchElementException

browser = Chrome()
browser.get('https://sp1.hso.mohw.gov.tw/doctor/Often_question/type_detail.php?q_type=排便問題&UrlClass=肝膽腸胃科')

category = "排便問題"
doctor_department = "肝膽腸胃科"
for paragraph in browser.find_elements(By.CSS_SELECTOR, "ul.QAunit"):
    time.sleep(10)

    subject = paragraph.find_element(By.CSS_SELECTOR, "li.subject").text

    asker_info = paragraph.find_element(By.CSS_SELECTOR, "li.asker").text
    match = re.search(r'/([男女])/.*?,(\d{4}/\d{2}/\d{2})', asker_info)
    gender = match.group(1)
    question_time = datetime.strptime(match.group(2), '%Y/%m/%d')

    question = paragraph.find_element(By.CSS_SELECTOR, "li.ask").text

    answer = paragraph.find_element(By.CSS_SELECTOR, "li.ans").text

    doctor_info = paragraph.find_element(By.CSS_SELECTOR, "li.doctor").text
    match = re.search(r'/([\u4e00-\u9fa5]+),\s*(\d{4}/\d{2}/\d{2})', doctor_info)
    doctor_name = match.group(1)
    answer_time = datetime.strptime(match.group(2), '%Y/%m/%d')

    view_info = paragraph.find_element(By.CSS_SELECTOR, "li.count").text
    match = re.search(r'(\d+)', view_info)
    view_amount = int(match.group(1)) if match else 0

    data = dict(
        category=category,
        subject=subject,
        question=question,
        gender=gender,
        question_time=question_time,
        answer=answer,
        doctor_name=doctor_name,
        doctor_department=doctor_department,
        answer_time=answer_time,
        view_amount=view_amount
    )
    pprint(data)

    try:
        next_page_element = browser.find_element(By.LINK_TEXT, "下一頁")
        next_page_element.click()
    except NoSuchElementException:
        break

browser.quit()

小結

透過 Selenium,我們能夠模擬使用者在瀏覽器上的操作,進而取得動態生成的網頁內容。本篇文章示範了如何使用 Selenium 結合正則表達式,從網頁中擷取結構化的資料。

注意事項

  • 網站禮節:在進行網路爬蟲時,請遵守網站的使用條款和 robots.txt 規範,避免對伺服器造成過大負擔。
  • 延遲設定:使用 time.sleep() 可以避免過於頻繁的請求。
  • 錯誤處理:在實際應用中,應該加入錯誤處理機制,處理可能的例外狀況。

內容預告

今天我們介紹了如何使用 Selenium 進行動態網頁的資料擷取,明天我們將加入問題與回答的重構。


上一篇
Day 22 - 站台架構設計
下一篇
Day 24 - 問題及解答重構
系列文
初探 Langchain 與 LLM:打造簡易問診機器人30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言